# Copyright (c) 2005,2006,2007 Dynamic Solutions Inc. (support@dynamic-solutions.com)
#
# This file is part of DataZoomer.
#
# DataZoomer is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# DataZoomer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


from types import DictType, MethodType, FunctionType, NoneType

class Entity:
    """The Entity class provides objects being modelled with a flexible behaviour that allows
    them to be easily formatted, queried, compared and manipulated."""

    def __init__(self,rec=None):
        """Initialize a new entity"""
        self.__rec = self._from_rec(rec)
        self.on_create()

    def on_create(self):
        """Initialize new entity values"""
        pass

    def _from_rec(self,rec):
        if type(rec) == DictType:
            for name in rec:
                self.__dict__[name] = rec[name]
        return rec

    def o2_from_rec(self,rec):
        if rec:
            for name in rec.keys():
                self.__dict__[name] = rec[name]

    def o_from_rec(self,rec,key='id'):
        if type(rec) == DictType:
            if 'key' in rec.keys(): self.__dict__[key] = rec['key']
            for name in rec:
                self.__dict__[name] = rec[name]
        return rec

    def __getitem__(self,key):
        """Provide access to attributes by name"""
        if key in self.__dict__:
            return self.__dict__[key]
        else:
            return self.__getattr__(key)

    def __getattr__(self,name):
        """Provide access to attributes, proxied attributes and calculated attributes"""
        try:
            the_attr = getattr(self.__class__,name)
            try:
                result = the_attr(self)
            except:
                result = the_attr
        except:
            try:
                result = getattr(self.__rec,name)
            except:
                try:
                    method_name = 'get_'+str(name)
                    method = getattr(self.__class__,method_name)
                    result = method(self)
                except AttributeError:
                    result = None
        return result

    def o__getattr__(self,name):
        """Provide access to attributes, proxied attributes and calculated attributes"""
        try:
            the_attr = getattr(self.__class__,name)
            try:
                result = the_attr(self)
            except:
                result = the_attr
        except:
            try:
                method_name = 'get_'+str(name)
                method = getattr(self.__class__,method_name)
                result = method(self)
            except AttributeError:
                result = None
        return result

    def field_values(self,names):
        return dict(zip(names,[getattr(self,name) for name in names]))

    def rec_keys(self):
        """Keys for proxied attributes only"""
        if self.__rec:
            return self.__rec.keys()
        else:
            return []

    def attr_keys(self):
        return \
            [key for key in self.__dict__.keys() if key[0]!='_'] + \
            [key for key in self.__class__.__dict__.keys() if (key[0]!='_' and not callable(self.__class__.__dict__[key]))]


    def calc_keys(self):
        return \
            [key[4:] for key in self.__class__.__dict__.keys() if key[:4]=='get_' and callable(self.__class__.__dict__[key])]


    def keys(self):
        """Keys for all visible attributes available in the Entity"""
        l = list(set(self.attr_keys() + self.rec_keys() + self.calc_keys()))
        l.sort()
        return l

    def __str__(self):
        def trap(obj,name):
            try:
                result = getattr(obj,name)
            except:
                result = '!exception raised!'
            return result

        """String representation of Entity"""
        t = [(name,trap(self,name)) for name in self.keys()]
        return '%s\n%s\n' % (self.__class__, '\n'.join([' %15.15s: %s' % d for d in t]))
#        return '%s: %s' % (self.__class__, t)


    def __len__(self):
        """Number of attribtues available in the Entity"""
        return len(self.keys())

    def __iter__(self):
        """Iterates through the available attributes as key/value pairs"""
        for key in self.keys():
            yield (key,self[key])

    def __eq__(self,other):
        if other == None:
            return 0
        else:
            for key in self.keys():
                if getattr(self,key) != getattr(other,key):
                    return 0
            return 1

    def __ne__(self,other):
        return not self.__eq__(other)
        
    def __nonzero__(self):
        return 1


if __name__ == '__main__':
    import unittest

    class MyEntity(Entity):
        first_name = 'Joe'

        def get_last_name(self):
            return 'Smith'

        def get_full_name(self):
            return self.first_name + ' ' + self.last_name

        def save(self):
            pass
            
        def get_crash_me(self):
            raise 'Nasty exception'

    class FillTest(unittest.TestCase):
        def test_create(self):
            entity = MyEntity()

        def test_assign(self):
            entity = MyEntity()
            entity.value = 'test123'
            self.assertEqual(entity.value,'test123')
            
        def test_getattr(self):
            entity = MyEntity()
            self.assertEqual(getattr(entity,'first_name'),'Joe')
            self.assertEqual(getattr(entity,'full_name'),'Joe Smith')
            self.assertEqual(getattr(entity,'phone'),None)
            try:
                getattr(entity,'crash_me'),None
            except:
                pass
            else:
                self.fail()

        def test_getattr_direct(self):
            entity = MyEntity()
            self.assertEqual(entity.__getattr__('first_name'),'Joe')
            self.assertEqual(entity.__getattr__('full_name'),'Joe Smith')
            self.assertEqual(entity.__getattr__('phone'),None)
            try:
                entity.__getattr__('crash_me'),None
            except:
                pass
            else:
                self.fail()

        def test_class_init(self):
            entity = MyEntity()
            self.assertEqual(entity.first_name,'Joe')

        def test_class_assign(self):
            entity = MyEntity()
            entity.__class__.value = 'test123'
            self.assertEqual(entity.value,'test123')
            
        def test_calc(self):
            entity = MyEntity()
            self.assertEqual(entity.full_name,'Joe Smith')

        def test_proxy_attribute(self):
            class ValueProxy:
                some_attribute = 'proxy attribute value'
                def keys(self):
                    return [key for key in self.__dict__.keys() + self.__class__.__dict__.keys() if key[0]!='_']

            entity = MyEntity(ValueProxy())
            self.assertEqual(entity.some_attribute,'proxy attribute value')

        def test_missing_attribute(self):
            entity = MyEntity()
            self.assertEqual(entity.phone,None)

        def test_exception_in_calculated_attribute(self):
            entity = MyEntity()
            try:
                x = entity.crash_me
            except:
                pass
            else:
                self.fail()

        def test_str(self):
            entity = MyEntity()
            self.assertEqual( str(entity),\
"""__main__.MyEntity
        crash_me: !exception raised!
      first_name: Joe
       full_name: Joe Smith
       last_name: Smith
           value: test123
""")


    unittest.main()


